<?php
/**
 * 简单的XML解析器
 * @author Leo 
 *
 */
class XML {
	private $parser; #a reference to the XML parser 
	private $document; #the entire XML structure built up so far 
	private $parent; #a pointer to the current parent - the parent will be an array 
	private $stack; #a stack of the most recent parent at each nesting level 
	private $last_opened_tag; #keeps track of the last tag opened. 
	/**
	 * 创建XML解析器新实现
	 * 
	 */
	public function __construct() {
		$this->parser = xml_parser_create ();
		@xml_parser_set_option ( $this->parser, XML_OPTION_CASE_FOLDING, false );
		@xml_set_object ( $this->parser, $this );
		@xml_set_element_handler ( $this->parser, 'open', 'close' );
		@xml_set_character_data_handler ( $this->parser, 'data' );
	}
	/**
	 * 销毁XML解析器
	 */
	public function destruct() {
		@xml_parser_free ( $this->parser );
	}
	/**
	 * 解析XML文件
	 * @param string $data 要解析的数据
	 */
	public function parse($data) {
		$this->document = array ();
		$this->stack = array ();
		$this->parent = &$this->document;
		return @xml_parse ( $this->parser, $data, true ) ? $this->document : NULL;
	}
	//标签打开
	private function open(&$parser, $tag, $attributes) {
		$this->data = ''; #stores temporary cdata 
		$this->last_opened_tag = $tag;
		if (is_array ( $this->parent ) and array_key_exists ( $tag, $this->parent )) { #if you've seen this tag before 
			if (is_array ( $this->parent [$tag] ) and array_key_exists ( 0, $this->parent [$tag] )) { #if the keys are numeric 
				#this is the third or later instance of $tag we've come across 
				$_tmpArr = $this->parent [$tag];
				$key = is_array ( $_tmpArr ) ? count ( array_filter ( array_keys ( $_tmpArr ), 'is_numeric' ) ) : 0;
			} else {
				#this is the second instance of $tag that we've seen. shift around 
				if (array_key_exists ( "$tag attr", $this->parent )) {
					$arr = array ('0 attr' => &$this->parent ["$tag attr"], &$this->parent [$tag] );
					unset ( $this->parent ["$tag attr"] );
				} else {
					$arr = array (&$this->parent [$tag] );
				}
				$this->parent [$tag] = &$arr;
				$key = 1;
			}
			$this->parent = &$this->parent [$tag];
		} else {
			$key = $tag;
		}
		if ($attributes)
			$this->parent ["$key attr"] = $attributes;
		$this->parent = &$this->parent [$key];
		$this->stack [] = &$this->parent;
	}
	//数据
	private function data(&$parser, $data) {
		if ($this->last_opened_tag != NULL) #you don't need to store whitespace in between tags 
			$this->data .= $data;
	}
	//关闭
	private function close(&$parser, $tag) {
		if ($this->last_opened_tag == $tag) {
			$this->parent = $this->data;
			$this->last_opened_tag = NULL;
		}
		array_pop ( $this->stack );
		if ($this->stack)
			$this->parent = &$this->stack [count ( $this->stack ) - 1];
	}
}
/**
 * XML结点
 * @author Leo
 *
 */
class XmlNode {
	
	protected $name;
	
	protected $nodeArr;
	
	protected $nodeAttr = array ();
	
	protected $raw; //原生数组
	

	protected $plan = false;
	
	protected $planText = '';
	
	public function __construct($name, $nodeArr) {
		$this->parseNode ( $name, $nodeArr );
	}
	/**
	 * 解析NODE
	 * @param string $name
	 * @param array $nodeArr
	 */
	protected function parseNode($name, $nodeArr) {
		$this->raw = $nodeArr;
		
		if (isset ( $nodeArr [$name . ' attr'] )) {
			$this->nodeAttr = $nodeArr [$name . ' attr'];
			unset ( $nodeArr [$name . ' attr'] );
		}
		
		$this->nodeArr = $nodeArr [$name];
		if (is_string ( $this->nodeArr )) {
			$this->plan = true;
			$this->planText = $this->nodeArr;
		}
		$this->name = $name;
	}
	/**
	 * 根据(简单的)XPATH信息查询XML NODE
	 * @param string $path
	 */
	public function xpath($path) {
		
		$path = explode ( '/', $path );
		$count = count ( $path ) - 1;
		$_tmpNodeArr = $this->nodeArr;
		
		foreach ( $path as $key => $xp ) {
			
			if (! is_array ( $_tmpNodeArr )) {
				return null;
			}
			
			$m = array ();
			if (! preg_match ( '/([\w]+)(:([\d]{1,5})?(,?([a-zA-Z][^=]*)=([^=]+))?)?/', $xp, $m )) {
				return null;
			}
			
			$tagname = isset ( $m [1] ) ? $m [1] : '';
			$index = isset ( $m [3] ) && is_numeric ( $m [3] ) ? $m [3] : - 1;
			$attr = isset ( $m [5] ) ? $m [5] : '';
			$value = isset ( $m [6] ) ? $m [6] : '';
			
			if (empty ( $tagname ) || ! isset ( $_tmpNodeArr [$tagname] )) { //没有找到NODE
				return null;
			}
			
			$node = $_tmpNodeArr [$tagname];
			$nodeAttr = isset ( $_tmpNodeArr [$tagname . ' attr'] ) ? $_tmpNodeArr [$tagname . ' attr'] : array ();
			
			if (is_array ( $node ) && isset ( $node [0] )) {
				if ($index >= 0) {
					if (! isset ( $node [$index] )) { //没有找到指定的索引的NODE
						return null;
					} else {
						$_node = $node [$index];
						$_nodeAttr = isset ( $node [$index . ' attr'] ) ? $node [$index . ' attr'] : array ();
						if ($this->hasAttr ( $attr, $value, $_nodeAttr, $_nodeAttr )) { //属性配对完成
							if ($key == $count) {
								$newNode = array ();
								$newNode [$tagname] = $_node;
								if (! empty ( $_nodeAttr )) {
									$newNode [$tagname . ' attr'] = $_nodeAttr;
								}
								return new XmlNode ( $tagname, $newNode );
							} else {
								$_tmpNodeArr = $_node;
							}
						} else {
							return null;
						}
					}
				} else {
					$_tmp = array ();
					$_rtnNodes = array ();
					foreach ( $node as $i => $n ) {
						if (preg_match ( '/[^\s]+\s+attr/', $i )) {
							continue;
						}
						$_nodeAttr = isset ( $node [$i . ' attr'] ) ? $node [$i . ' attr'] : array ();
						if ($this->hasAttr ( $attr, $value, $_nodeAttr )) {
							if ($count == $key) {
								$newNode [$tagname] = $n;
								if (! empty ( $_nodeAttr )) {
									$newNode [$tagname . ' attr'] = $_nodeAttr;
								} else if (isset ( $newNode [$tagname . ' attr'] )) {
									unset ( $newNode [$tagname . ' attr'] );
								}
								$_rtnNodes [] = new XmlNode ( $tagname, $newNode );
							} else {
								$_tmp = $n;
								break;
							}
						}
					}
					//最后一轮找到对应的XML节
					if (! empty ( $_rtnNodes )) {
						if (count ( $_rtnNodes ) == 1) {
							$_rtnNodes = $_rtnNodes [0];
						}
						return $_rtnNodes;
					} elseif (empty ( $_tmp )) {
						return null;
					} else { //继续查找
						$_tmpNodeArr = $_tmp;
					}
				}
			} else {
				if ($this->hasAttr ( $attr, $value, $nodeAttr )) {
					if ($key == $count) {
						$newNode = array ();
						$newNode [$tagname] = $node;
						if (! empty ( $nodeAttr )) {
							$newNode [$tagname . ' attr'] = $nodeAttr;
						}
						return new XmlNode ( $tagname, $newNode );
					} else {
						$_tmpNodeArr = $node;
					}
				} else {
					return null;
				}
			}
		}
	}
	/**
	 * 根据(简单的)XPATH信息查询XML NODE
	 * @param string $path
	 * @param int $idx
	 * @return XmlNode
	 */
	public function find($path, $idx = null) {
		$nodes = $this->xpath ( $path );
		if (is_null ( $nodes )) {
			return null;
		} else if (is_array ( $nodes ) && ! is_null ( $idx )) {
			return $nodes [$idx];
		} else {
			return $nodes;
		}
	}
	/**
	 * 根据(简单的)XPATH信息查询XML NODE
	 * @param string $path
	 * @return XmlNode
	 */
	public function get($path){
		return $this->find($path,0);
	}
	/**
	 * 返回元素数组
	 * @param string $path
	 */
	public function find_array($path) {
		$nodes = $this->xpath ( $path );
		if (is_null ( $nodes )) {
			return array ();
		} else if (! is_array ( $nodes )) {
			return array ($nodes );
		} else {
			return $nodes;
		}
	}
	/**
	 * 取得属性
	 * @param string $name 属性名
	 * @param mixed $default 默认值
	 */
	public function attr($name, $default = '') {
		return isset ( $this->nodeAttr [$name] ) ? $this->nodeAttr [$name] : $default;
	}
	public function __get($name) {
		return $this->attr ( $name, NULL );
	}
	/**
	 * 是否有属性$name,且值等于 $value
	 * @param string $name
	 * @param mixed $value
	 * @param array $attrs
	 */
	public function hasAttr($name, $value, $attrs = null) {
		if (empty ( $name ) && empty ( $value )) {
			return true;
		}
		$attrs = is_null ( $attrs ) ? $this->nodeAttr : $attrs;
		if (isset ( $attrs [$name] ) && $attrs [$name] == $value) {
			return true;
		}
		return false;
	}
	public function issetAttr($name) {
		return isset ( $this->nodeAttr [$name] );
	}
	/**
	 * 返回当前XML节的值,如果是普通节点,则返回string类型的文本值,否则返回array
	 * @return mixed
	 */
	public function value() {
		if ($this->plan) {
			return $this->planText;
		} else {
			return $this->nodeArr;
		}
	}
	/**
	 * 是否是普通NODE
	 * @return boolean
	 */
	public function isPlan() {
		return $this->plan;
	}
	/**
	 * 得到NODE名
	 * @return string
	 */
	public function getName() {
		return $this->name;
	}
	/**
	 * 得到系统原生数据
	 * @return mixed
	 */
	public function raw() {
		return $this->raw;
	}
}